14.2 Die Metadaten mittels Reflektion abfragen
 
Nachdem Sie nun über die wichtigsten Gesichtspunkte einer Assembly erfahren haben, wenden wir uns einem eng verwandten Thema zu: der Reflektion. Dabei handelt es sich um einen Prozess, der den Zugriff auf die Metadaten einer Assemblierung ermöglicht. Sie können damit zur Laufzeit Typinformationen abfragen. Die Reflektion ermöglicht die Analyse von Klassen, Strukturen, Schnittstellen usw. Das ist aber noch nicht alles. Sie können mit der Reflektion sogar Methoden dynamisch aufrufen und im Extremfall sogar zur Laufzeit MSIL-Code generieren.
Die in Zusammenhang mit der Reflektion stehenden Klassen befinden sich im Namespace System.Reflection. Dahinter verstecken sich ungezählte Methoden, welche die Auswertung einer Assemblierung und ihrer Metadaten ermöglicht. Daher ist es unmöglich, hier auf alle Features einzugehen. Dennoch möchte ich Ihnen einige Möglichkeiten vorstellen, welche die Reflektion bietet. Sollten Sie weitergehende Informationen benötigen, lesen Sie bitte in der .NET-Dokumentation nach.
14.2.1 Der Mittelpunkt der Reflektion: die Klasse »Type«
 
Müsste man die wichtigste Klasse der Reflektion nennen, es wäre ohne Zweifel Type. Sie ist im Namespace System beheimatet und beschreibt gewissermaßen die Eingangstür zur Reflektion und stellt die wichtigsten Hilfsmittel für den Zugriff auf die Metadaten zur Verfügung. Die Methoden ermöglichen die Abfrage von Typinformationen, zu denen Konstruktoren, Methoden, Ereignisse, Felder und Eigenschaften eines bestimmten Typs gehören.
Um einen bestimmten Typ untersuchen zu können, benötigen wir einen Verweis auf das einem Typ zugeordnete Type-Objekt. Hier bieten sich zwei Möglichkeiten an:
|
Ihnen liegt eine konkrete Objektreferenz vor, die Sie untersuchen wollen. Rufen Sie in diesem Fall auf die Objektvariable die von Object geerbte Methode GetType auf. |
|
Sie möchten, ohne die Existenz eines konkreten Objekts in den Händen zu halten, einen Typ untersuchen. Hier bieten sich sogar zwei Alternativen an: das C#-Schlüsselwort typeof und die statische Methode GetType der Klasse Type. |
Alle zur Verfügung stehenden Varianten wollen wir uns nun im Programmcode ansehen.
Ermittlung von »Type« anhand eines konkreten Objekts
Zu den Methoden, die jede Klasse von Object erbt, gehört die Instanzmethode GetType. Ihr Aufruf auf eine Objektreferenz liefert ein Objekt vom Typ Type zurück, dessen Eigenschaften und Methoden eine genaue Untersuchung des zugrunde liegenden Datentypen ermöglichen.
Stellen wir uns vor, wir wollten wissen, welche Methoden von der Klasse Program veröffentlicht werden. Auf eine Type-Refrenz brauchen wir dann nur die Methode GetMethods aufzurufen, die ein Array vom Typ MethodInfo zurückliefert.
| public MethodInfo[] GetMethods();
|
MethodInfo ermöglicht den Zugriff auf die Metadaten der Methode und ermittelt die Attribute einer Methode. Über die Eigenschaften und Methoden können Sie feststellen, ob die Methode statisch oder abstrakt ist, welche Parameter sie definiert usw.
Im folgenden Code enthält die Klasse Program über Main hinaus aus Anschauungsgründen noch eine zusätzliche benutzerdefinierte Methode.
| class Program {
|
| public static void Main(string[] args) {
|
| Program prgm = new Program();
|
| Type type = prgm.GetType();
|
| MethodInfo[] info = type.GetMethods();
|
| foreach (MethodInfo meth in info)
|
| Console.WriteLine(meth.Name);
|
| Console.ReadLine();
|
| }
|
| public void MyMethod() {
|
| // Anweisungen
|
| }
|
| }
|
Innerhalb einer foreach-Schleife greifen wir auf jedes MethodInfo-Objekt zu und rufen dessen Eigenschaft Name ab, die uns den Methodenbezeichner zurückliefert. Wenn Sie das Programm starten, erkennen Sie, dass alle öffentlichen Methoden erfasst werden, auch die von der oder den Basisklassen geerbten. Um auch die gegebenenfalls private deklarierten Methoden zu sehen, müssen Sie die Überladung von GetMethods aufrufen und dem Parameter eine Konstante der Enumeration BindingFlags übergeben. Über die verschiedenen Member dieser Enumeration können Sie auch weiteren Einfluss auf die Rückgabe von GetMethods ausüben.
Ermittlung von »Type« anhand des Typs
Es liegt nicht zwangsläufig immer ein Objekt vor, das untersucht werden soll. Manchmal interessiert ganz einfach nur die Struktur eines Typs. Sie können den Operator typeof benutzen, um zum Ziel zu kommen:
| Type type = typeof(Program);
|
| MethodInfo[] info = type.GetMethods();
|
| ...
|
Darüber hinaus können Sie auch die GetType-Methode der Klasse Type aufrufen und übergeben dabei den gewünschten Typ als Argument in einer Zeichenfolge. Manchmal ist es vorteilhafter, diese Methode dem typeof-Operator vorzuziehen, denn GetType ist überladen.
| public static Type GetType(string name);
|
| public static Type GetType(string name, bool throwOnError );
|
| public static Type GetType(string name, bool throwOnError, bool ignoreCase);
|
Dem ersten Parameter wird in allen Fällen der vollständig qualifizierende Name des gewünschten Typs übergeben. Der zweite Parameter kann die Laufzeit anweisen, eine Exception auszulösen, falls der im ersten Parameter genannte Typ nicht gefunden wird. Der dritte Parameter schließlich gibt an, ob bei der Typsuche zwischen der Groß- und Kleinschreibung unterschieden werden soll.
| Type type = Type.GetType("ConsoleApplication1.Program");
|
| MethodInfo[] info = type.GetMethods();
|
| ...
|
Untersuchung eines Typen
Die Klasse Type ermöglicht Ihnen, einen bestimmten Typ in praktisch jeder Hinsicht zu analysieren. Sie können ermitteln, ob es sich um eine Klassendefinition, eine Schnittstelle, eine Struktur usw. handelt, Sie können feststellen, ob der Typ abstrakt ist oder versiegelt (sealed), öffentlich oder privat.
Damit Sie ein Gefühl dafür bekommen, welche Unterstützung Sie erwarten können, sollten Sie sich das folgende Beispielprogramm einmal ansehen. Es zeigt nur einen kleinen Ausschnitt der Möglichkeiten, einen Typ zu untersuchen. Als Grundlage wird dabei wieder auf unsere Klasse Circle zurückgegriffen, die wir in den Kapiteln 4 bis 6 kontinuierlich entwickelt hatten.
| // ---------------------------------------------------------
|
| // Beispiel: ...\Kapitel 14\GetTypeInformationDemo
|
| // ---------------------------------------------------------
|
| using System;
|
| using System.Collections.Generic;
|
| using System.Text;
|
| using System.Reflection;
|
| class Program {
|
| static void Main(string[] args) {
|
| Type type = Type.GetType("CircleApplication.Circle");
|
| Console.WriteLine("Typuntersuchung:");
|
| Console.WriteLine(new string('=', 40));
|
| if (type.IsClass)
|
| Console.WriteLine("{0} ist eine Klasse.", type.Name);
|
| else if(type.IsInterface)
|
| Console.WriteLine("{0} ist eine Klasse.", type.Name);
|
| else if (type.IsArray)
|
| Console.WriteLine("{0} ist eine Klasse.", type.Name);
|
| else if (type.IsEnum)
|
| Console.WriteLine("{0} ist eine Klasse.", type.Name);
|
| Console.WriteLine("\n ----- Zugriffsmodifizierer -----");
|
| Console.WriteLine("public = {0}", type.IsPublic);
|
| Console.WriteLine("private = {0}", type.IsNotPublic);
|
| Console.WriteLine("sealed = {0}", type.IsSealed);
|
| Console.WriteLine("abstract = {0}", type.IsAbstract);
|
| Console.WriteLine("\n ----- Ereignisse -----");
|
| foreach (EventInfo temp in type.GetEvents()) {
|
| Console.Write("Name = {0} : ", temp.Name);
|
| Console.WriteLine(temp.EventHandlerType);
|
| }
|
| Console.WriteLine("\n ----- Felder -----");
|
| foreach (FieldInfo temp in type.GetFields()) {
|
| Console.Write("Name = {0} : ", temp.Name);
|
| Console.WriteLine(temp.FieldType);
|
| }
|
| Console.WriteLine("\n ----- Methoden -----");
|
| foreach (MethodInfo temp in type.GetMethods()) {
|
| Console.Write("Name = {0} (", temp.Name);
|
| foreach(ParameterInfo para in temp.GetParameters())
|
| Console.Write("{0} ",para.ParameterType);
|
| Console.WriteLine(")");
|
| }
|
| Console.ReadLine();
|
| }
|
| }
|
Der Code beschränkt sich darauf, nur öffentliche Member von Circle auszugeben. Hierbei handelt es sich, nach einer zuvor durchgeführten allgemeinen Untersuchung von Circle, um Ereignisse, Felder und Methoden. Von den Methoden werden zudem die Typen der Parameter, so vorhanden, abgefragt, die Ereignisse und Felder geben Auskunft darüber, von welchem Typ sie sind. Beachten Sie, dass die Klassen MethodInfo, FieldInfo, EventInfo und ParameterInfo, die in diesem Beispiel auftreten, weitergehende Informationen bereitstellen.
Die Ausgabe an der Konsole zeigt die Abbildung 14.13.
 Hier klicken, um das Bild zu vergrößern
Abbildung 14.13 Die Ausgabe des Beispielprogramms »GetTypeInformationDemo«
14.2.2 Code dynamisch erzeugen
 
Die Möglichkeiten, die uns Reflektion bietet, sind aufregend. Der Rahmen dieses Buches gestattet es nicht, Ihnen alle Möglichkeiten zu zeigen. Die Krönung dürfte aber sein, dass wir sogar zur Laufzeit einer Anwendung Code dynamisch erzeugen und ausführen können. Hierzu benötigen wir Typen, die im Namespace System.Reflection.Emit definiert sind.
Exemplarisch möchte ich Ihnen das Vorgehen anhand des folgenden Beispiels zeigen. Für den Code, in dem der dynamische Typ erzeugt wird, wird eine eigene Klasse bereitgestellt. Damit wird der gesamte Code übersichtlicher und besser verständlich.
| // ---------------------------------------------------------
|
| // Beispiel: ...\Kapitel 14\DynamischCodeErzeugen
|
| // ---------------------------------------------------------
|
| public class GenerateType {
|
| Type type;
|
| public Type MyType {
|
| get {
|
| return this.type;
|
| }
|
| }
|
| public GenerateType() {
|
| // Abrufen der aktuellen Anwendungsdomäne
|
| AppDomain dom = AppDomain.CurrentDomain;
|
| // Erstellen einer neuen Assembly
|
| AssemblyName assembly = new AssemblyName();
|
| assembly.Name = "TempAssembly";
|
| AssemblyBuilder assBuilder =
|
| dom.DefineDynamicAssembly(assembly,
|
| AssemblyBuilderAccess.Run);
|
| // Erzeugen eines Moduls in der Assembly
|
| ModuleBuilder modBuilder =
|
| assBuilder.DefineDynamicModule("NewModule");
|
| // Erzeugen eines Typen im Modul
|
| TypeBuilder tBuilder =
|
| modBuilder.DefineType("DynamicClass",
|
| TypeAttributes.Public);
|
| // Dem neuen Typ eine Methode hinzufügen
|
| MethodBuilder mBuilder =
|
| tBuilder.DefineMethod("MyDynamicMethod",
|
| MethodAttributes.Public, null, null);
|
| // Erzeugen des IL-Codes
|
| ILGenerator ilGen = mBuilder.GetILGenerator();
|
| ilGen.EmitWriteLine("In der dynamischen Methode.");
|
| ilGen.Emit(OpCodes.Ret);
|
| // Type festlegen
|
| type = tBuilder.CreateType();
|
| }
|
| }
|
Um den Typ dynamisch zu erzeugen, muss ein potenzieller Client die Klasse GenerateType instanziieren. Hier ist innerhalb des Konstruktors der Code implementiert, der für die Bereitstellung des dynamischen Typs verantwortlich ist.
Im ersten Schritt wird die Referenz auf die aktuelle Anwendungsdomäne abgefragt. Anschließend wird ein Objekt vom Typ AssemblyName erzeugt, dem anschließend über die Eigenschaft Name ein Name zugewiesen wird. Eine dynamische Assemblierung wird durch ein Objekt vom Typ AssemblyBuilder dargestellt, das innerhalb der aktuellen Anwendungsdomäne durch Aufruf der Methode DefineDynamicAssembly bereitgestellt wird. Als Parameter wird dabei das AssemblyName-Objekt übergeben sowie eine Konstante der Enumeration AssemblyBuilderAccess, mit der zum Ausdruck gebracht wird, wie der Zugriff auf die Assembly erfolgen soll. Mit Run wird festgelegt, dass die Assembly im Hauptspeicher ausgeführt wird. Sie können die Assembly aber auch auf der Festplatte speichern.
Die Assembly ist nun definiert, jetzt benötigen wir noch ein Modul, das durch eine Instanz der Klasse ModuleBuilder beschrieben wird. Das Objekt erhalten wir auch hier als Rückgabewert eines Methodenaufrufs. Es handelt sich hier um die Methode DefineDynamicModule, die auf die Referenz der neuen dynamischen Assemblierung aufgerufen wird.
Nun haben wir fast das Ziel erreicht, und es muss jetzt nur noch die dynamische Klasse definiert werden. Dazu dient die Methode DefineType des ModuleBuilder-Objekts, der wir den Bezeichner im ersten Parameter und eine Konstante aus der Enumeration TypeAttributes übergeben. Hier handelt es sich um TypeAttributes.Public, was eine öffentliche Klasse zur Folge hat. In unserem Beispiel heißt die neue Klasse DynamicClass, die nun auch noch eine Methode bereitstellen soll. Die Methode DefineMethod leistet genau das. Wir teilen im ersten Argument noch den Namen der Methode mit – er lautet hier MyDynamicMethod – und geben anschließend noch die Modifizierer bekannt. Unsere Methode soll public sein.
Aus diesem Grundgerüst muss IL-Code generiert werden. Darüber hinaus soll die Methode auch eine Implementierung bereitstellen. Uns soll für dieses einfache Beispiel der Aufruf von Console.WriteLine mit einer passenden Ausgabe ausreichend sein. Der Schlüssel zur Lösung des Problems liegt in der Klasse ILGenerator. Wir erhalten das Objekt, indem wir auf das MethodBuilder-Objekt die Methode GetILGenerator aufrufen. Die Methode EmitWriteLine ist eine Hilfsfunktionen zum Ausgeben eines Aufrufs der WriteLine-Methode.
Zum Schluss rufen wir die Methode TypeBuilder.CreateType auf. Das ist der letzte Schritt, wenn alle Mitglieder des neuen Typs definiert sind. Der Rückgabewert ist ein Type-Objekt, das für die spätere Verwendung in der gekapselten Variablen type gespeichert wird. Von außen kann der Typ über die Eigenschaft MyType abgefragt werden.
Was nun noch fehlt, ist das Testprogramm. Voilà, hier ist es.
| class Program {
|
| static void Main(string[] args) {
|
| GenerateType gen = new GenerateType();
|
| Type type = gen.MyType;
|
| // Erstellen des neuen Typs
|
| object obj = Activator.CreateInstance(type);
|
| // die neue Methode referenzieren
|
| MethodInfo dynMethod = type.GetMethod("MyDynamicMethod");
|
| // Aufruf der dynamisch erzeugten Methode
|
| dynMethod.Invoke(obj, null);
|
| Console.ReadLine();
|
| }
|
| }
|
Zuerst muss die Klasse GenerateType instanziiert werden, damit sichergestellt wird, dass der dynamische Typ erzeugt wird. Damit haben wir aber noch kein Objekt unseres dynamischen Typs vorliegen, von dem wir nur den Type abrufen können. Was wir benötigen, ist eine Operation, die aufgrund der Type-Angabe in der Lage ist, ein Objekt des entsprechenden Typs zu erzeugen. Hier kommt uns eine Klasse aus dem Namespace System zur Hilfe: Activator. Deren statische Methode CreateInstance leistet genau das, wonach wir gesucht haben.
Jetzt brauchen wir nur noch die Referenz auf die dynamische Methode, die uns die Methode GetMethod des Type-Objekts bereitstellt. Darauf rufen wir die gewünschte Methode mit Invoke auf. |